/*******************************************************************************
*  file    : modemmanager.cpp
*  created : 03.08.2013
*  author  : 
*******************************************************************************/

#include "modemmanager.hpp"
#include <QCoreApplication>
#include <QSerialPortInfo>
#include <QScopedPointer>
#include <QDateTime>
#include <QTimer>
#include <QDebug>


#include <iostream>
#include <algorithm>
#include <qlogger.hpp>
#include <gsm.hpp>
#include <qatchat.h>
#include <qtelephonynamespace.h>
#include <utils.hpp>

#include <QRegularExpression>
#include <QRegularExpressionMatch>

namespace
{
QLogger* l = QLoggerEngine::getLogger("core");
QScopedPointer<ModemManager> manager;

QString convert_sms(const QString& str)
{
    QByteArray txt = str.toLatin1();
    QString real_text;
    real_text.reserve(256);
    for(int i = 0; i < txt.size(); ++i)
        {
            if(txt[i] < static_cast<char>(0x20))
                real_text.append(QString("[0x%1]").arg(static_cast<int>(txt[i]),2,16,QChar('0')) );
            else
                real_text.append(QChar(txt[i]));
        }
    return real_text;
}
}

ModemManager *ModemManager::instance()
{
    if( !manager )
        manager.reset( new ModemManager );

    return manager.data();
}

ModemManager::ModemManager(QObject *parent) :
    QObject(parent),
    modem_(nullptr),
    is_sending_(false)
{
    createModem();
    timer_ = new QTimer(this);
    timer_->setInterval(5100);
    connect(timer_, SIGNAL(timeout()), this, SLOT(onTime()) );

    connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(stop())  );
}

ModemManager::~ModemManager()
{

}

void ModemManager::createModem()
{

    deleteModem();

    modem_ = new GsmModem(this);
    connect(modem_, SIGNAL(initialized(bool)), this, SLOT(modemInitResult(bool)) );
    connect(modem_, SIGNAL(error(int)), this, SLOT(onModemError(int))  );

    sms_reader_ = new GsmSmsReader(modem_, this);
    sms_reader_->setMessageStore("ME");
    connect(sms_reader_, SIGNAL(messageCount(int)), this, SLOT(messageCount(int)) );

    sms_sender_ = new GsmSmsSender(modem_, this);
    connect(sms_sender_, SIGNAL(finished(QString,int)), this, SLOT(smsSended(QString,int)) );
}

void ModemManager::deleteModem()
{
    if(modem_)
        {
            disconnect(modem_);
            disconnect(sms_reader_);
            disconnect(sms_sender_);
            delete sms_reader_;
            delete sms_sender_;
            delete modem_;
            modem_ = nullptr;
        }
}

void ModemManager::start()
{
    logInfo(l)<<tr("Подключение к модему.");
    if( !modem_ )
        {
            createModem();
        }
    modem_->init(modemPort_, modemBaudrate_);
}

void ModemManager::onTime()
{
    if( modem_ && modem_->isValid() )
        {
            sms_reader_->check();
            loadTaskList();
            processIncoming();
            startSending();
            if(last_account_request_.elapsed() > (120 * 1000 ) && (is_sending_==false))
                requestModemMoney();
        }
}

void ModemManager::stop()
{
    logInfo(l)<<"Modem menager stoped.";
    timer_->stop();
}

void ModemManager::modemInitResult(bool res)
{
    if(res)
        {
            logInfo(l)<<tr("Modem on %1 initialized.").arg(modemPort_);
            modem_status_ = QString(tr("Модем подключен. %1 %2. %3. #%4")).arg(modem_->manufacturer())
                            .arg(modem_->model())
                            .arg(modem_->revision())
                            .arg(modem_->number());
            logInfo(l)<<modem_status_;
            modem_->primaryAtChat()->registerNotificationType("+CUSD:",this,SLOT(ussd(QString)));
            if(modem_->model().contains("MODEL=L6"))
                {
                    modem_->primaryAtChat()
                            ->registerNotificationType("+CPBR:",this,SLOT(phoneBookEntry(QString)));
                    modem_->primaryAtChat()->chat("AT+MODE=2");
                    modem_->primaryAtChat()->chat("AT+CSCS=UTF8");
                    modem_->primaryAtChat()->chat("AT+CPBS=ME");
                    //modem_->primaryAtChat()->chat("AT+CPBR=1,500");
                }
            else if(modem_->manufacturer().contains("huawei")) //E1550
                {
                    logInfo(l) << "Detect Huawei. Change SMS stotre to ""SM"". ";
                    sms_reader_->setMessageStore("SM");
                }
            requestModemMoney();

            if( !timer_->isActive() )
                timer_->start();
        }
    else
        {
            modem_status_ = QString(tr("Модем не подключен."));
            logError(l)<<modem_->errorString();
            QTimer::singleShot(5000, this, SLOT(start()) );
        }
}

void ModemManager::messageCount(int)
{
    if(!modem_)
        {
            logError(l) << tr("NULL modem object!");
            return;
        }
    while (sms_reader_->count())
        {
            const QSMSTaggedMessage* msg = sms_reader_->at(0);
            ArchiveDB::instance()->saveIncomingSms(msg->message.text(),
                                                   msg->message.sender(),
                                                   msg->message.timestamp()
                                                   .toString("yyyy-MM-dd hh:mm:ss"));

            logInfo(l)<<"message: "<<convert_sms(msg->message.text())<<" from: "<<msg->message.sender();
            sms_reader_->deleteMessage(msg->identifier);
            delete msg;
        }
}

void ModemManager::onModemError(int)
{
    if(modem_)
        logError(l)<<modem_->errorString();
    reconnectModem();
}

void ModemManager::loadTaskList()
{
    std::vector<int> task_id_old, task_id_new, task_id_dif;

    auto task_id = [](const std::unique_ptr<MoleTask>& tsk)->int{return tsk->id_;};

    task_id_old.resize(task_list_.size());
    std::transform(task_list_.begin(),task_list_.end(),task_id_old.begin(),task_id);

    std::vector<TasksLine> tasks = ArchiveDB::instance()->getTasks(0);//получаем невыполненные задачи

    task_list_.clear();
    for( const TasksLine& line: tasks)
        {
            std::unique_ptr<MoleTask> tsk = std::unique_ptr<MoleTask>(new MoleTask);
            tsk->id_         = line[TASKS_ID]     .toInt();
            tsk->request_    = line[TASKS_REQUEST].toInt();
            tsk->status_     = line[TASKS_STATE]  .toInt();
            tsk->start_time_ = QDateTime::fromString(line[TASKS_STARTDATE],"yyyy-MM-dd hh:mm:ss");
            task_list_.push_back(std::move(tsk));
        }
    if(task_list_.size() == 0)
        return;

    auto result = std::minmax_element(task_list_.begin(), task_list_.end(),
                                      [](const std::unique_ptr<MoleTask>& p1,
                                         const std::unique_ptr<MoleTask>& p2)
                                        {return (p1->request_ < p2->request_);});
    std::vector<OutgoingLine> smses = ArchiveDB::instance()->getSended( (*result.first)->request_,
                                                                        (*result.second)->request_);
    std::vector<MoleLine> moles = ArchiveDB::instance()->getMoles();
    for( const std::unique_ptr<MoleTask>& task: task_list_ )
        {
            auto sms = std::find_if(smses.begin(),smses.end(),
                                    [&task](const OutgoingLine& l)->bool
                                    {return (l[NO_SND_ID].toInt()==task->request_);});
            if(sms != smses.end())
                {
                    if((*sms)[NO_SND_TEXT].contains("pos?"))
                        task->request_text_ = "pos?";
                    else if((*sms)[NO_SND_TEXT].contains("info?"))
                        task->request_text_ = "info?";

                    auto mole = std::find_if(moles.begin(),moles.end(),
                                             [=](const MoleLine& l)->bool
                                             {return ((*sms)[NO_SND_RECIPIENT]==l[MOLES_ID]);});

                    if(mole != moles.end())
                        {
                            task->request_num_    = (*mole)[MOLES_PHONE];
                            task->request_dev_id_ = (*mole)[MOLES_ID].toInt();
                        }
                    else
                        {
                            logError(l)<<tr("mole %1 for task #%2 not FOUND")
                                                                             .arg((*sms)[NO_SND_RECIPIENT])
                                                                             .arg(task->id());
                        }
                }
        }

    task_id_new.resize(task_list_.size());
    std::transform(task_list_.begin(),task_list_.end(),task_id_new.begin(),task_id);
    std::sort(task_id_new.begin(),task_id_new.end());
    std::sort(task_id_old.begin(),task_id_old.end());
    task_id_dif.resize(task_id_new.size());
    auto end_cpy = std::copy_if(task_id_new.begin(),task_id_new.end(),task_id_dif.begin(),
                                [&task_id_old](int i)->bool
                                {return !std::binary_search(task_id_old.begin(),task_id_old.end(),i);});
    task_id_dif.resize(std::distance(task_id_dif.begin(),end_cpy));



    for( int id: task_id_dif )
        {
            auto task = std::find_if(task_list_.begin(),task_list_.end(),
                                     [id](const std::unique_ptr<MoleTask>& tsk){return (tsk->id_ == id);});
            if(task != task_list_.end())
            logDebug(l)<<"Task #"<<(*task)->id_  <<" "
                       <<(*task)->request_text_  <<" "
                       <<(*task)->request_num_   <<" "
                       <<(*task)->request_dev_id_<<" ["<<(*task)->status_<<"]";
        }
}

void ModemManager::processIncoming()
{
    std::vector<IncomingLine> incoming = ArchiveDB::instance()->getIncoming();

    for( const std::unique_ptr<MoleTask>& task: task_list_ )
        {
            for( IncomingLine& line : incoming)
                {
                    // ищем соответствие в СМС
                    // Странность QString :
                    // метод contains() отказывается находить подстроки если строка содержит 0x0D !!!
                    logDebug(l)<<"Сравнение "<<line[INCOMING_SOURCE]<<" == "
                               <<task->request_num_<<" ["<<task->status_<<"]";

                    if( (line[INCOMING_SOURCE] == task->request_num_) && (task->status_ == 0) )
                        {
                            bool is_answer = false;
                            if( task->request_text_  .contains(QStringLiteral("info?")) &&
                                convert_sms(line[INCOMING_TEXT]).contains(QStringLiteral("Krot") )    )
                                is_answer = true;
                            else if( task->request_text_  .contains(QStringLiteral("pos?")) &&
                                     convert_sms(line[INCOMING_TEXT])
                                     .contains(QStringLiteral("maps.google.com/maps?q=") ))
                                     is_answer = true;
                            //Проверка даты постановки задачи и даты прихода SMS
                            if(task->startTime().toString(Utils::datetime_format()) >= line[INCOMING_DATE])
                                {
                                    is_answer = false;
                                }

                            if( is_answer )
                                {
                                    task->status_ = 1; //все нормально
                                    ArchiveDB::instance()->setTaskDone(task->id_,line[INCOMING_ID].toInt());
                                    logInfo(l)<<QString(tr("Задача #%1 выполнена."))
                                                .arg(task->id_);
                                }
                        }
                }
        }
}

void ModemManager::requestModemMoney()
{
    if(modem_ && modem_->isValid())
        {
            last_account_request_.restart();
            modem_->chat("AT+CUSD=1,*111#");
        }
}

void ModemManager::reconnectModem()
{
    this->stop();
    deleteModem();

    QTimer::singleShot(1000, this, SLOT(start()));
}

int ModemManager::modemBaudrate() const
{
    return modemBaudrate_;
}

void ModemManager::setModemBaudrate(int baudrate)
{
    modemBaudrate_ = baudrate;
}


QString ModemManager::modemPort() const
{
    return modemPort_;
}

void ModemManager::setModemPort(const QString& modemPort)
{
    modemPort_ = modemPort;
}

const QString &ModemManager::modemStatus() const
{
    static QString res;
    res = QString(tr("%1 Баланс:%2 грн.")).arg(modem_status_).arg(modem_money_);
    return res;
}

int ModemManager::sendInfoRequest(const QStringList& num_id)
{
    std::vector<MoleLine>& moles = ArchiveDB::instance()->getMoles();
    int count = 0;
    for( const QString& idx : num_id )
        {
            auto mole_line = std::find_if(moles.begin(), moles.end(),
                                          [=](const MoleLine& ml)
                                          {return ml[MOLES_ID] == idx;});
            if( mole_line != moles.end() )
                {
                    QString text = QString("%1;info?").arg( (*mole_line)[MOLES_PASSWD] );
                    if (ArchiveDB::instance()->addSmsToOutgoing(idx.toInt(), text)) count++;
                }
        }

    return count;
}

int ModemManager::sendPosRequest(const QStringList& num_id)
{
    std::vector<MoleLine>& moles = ArchiveDB::instance()->getMoles();
    int count = 0;
    for( const QString& idx : num_id )
        {
            auto mole_line = std::find_if(moles.begin(), moles.end(),
                                          [=](const MoleLine& ml)
                                          {return ml[MOLES_ID] == idx;});
            if( mole_line != moles.end() )
                {
                    QString text = QString("%1;pos?").arg( (*mole_line)[MOLES_PASSWD] );
                    if (ArchiveDB::instance()->addSmsToOutgoing(idx.toInt(), text)) count++;
                }
        }

    return count;
}

int ModemManager::sendModeRequest(const QStringList &num_id, int mode)
{
    if( ! ((mode == 0) || (mode == 1)) )
        {
            logError(l)<<QString(tr("Неизвестный номер режима (%1)")).arg(mode);
            return 0;
        }

    std::vector<MoleLine>& moles = ArchiveDB::instance()->getMoles();
    int count = 0;
    for( const QString& idx : num_id )
        {
            auto mole_line = std::find_if(moles.begin(), moles.end(),
                                          [=](const MoleLine& ml)
                                          {return ml[MOLES_ID] == idx;});
            if( mole_line != moles.end() )
                {
                    QString bagage;
                    if(mode == 0)
                        bagage = "info";
                    else if (mode == 1)
                        bagage = "pos";

                    QString text = QString("%1;mode=%2;%3?")
                            .arg( (*mole_line)[MOLES_PASSWD] ).arg(mode).arg(bagage);
                    if (ArchiveDB::instance()->addSmsToOutgoing(idx.toInt(), text)) count++;
                }
        }

    return count;
}

const MoleTaskList& ModemManager::taskList() const
{
    return task_list_;
}

void ModemManager::startSending()
{
    if(!modem_)
        {
            logError(l) << tr("NULL modem object!");
            return;
        }
    if( is_sending_ )
        return;

    sms_count_  = 0;
    moles_  = ArchiveDB::instance()->getMoles();
    nonsnd_ = ArchiveDB::instance()->getNonSended();
    for( const OutgoingLine line : nonsnd_ )
        {
            QSMSMessage msg;
            msg.setText(line[NO_SND_TEXT]);
            auto ph_num = std::find_if(moles_.begin(),moles_.end(),
                                        [line](const MoleLine& l)->bool
                                        {return (line[NO_SND_RECIPIENT] == l[MOLES_ID]);}
                                       );
            if(ph_num != moles_.end())
                msg.setRecipient( (*ph_num)[MOLES_PHONE] );
            else
                {
                    logError(l)<<"Wrong phone ID";
                    ArchiveDB::instance()->setOutgoingState(line[NO_SND_ID], 1000);
                    continue;
                }

            msg.setGsmValidityPeriod(168); //168 - двое суток
            sms_sender_->send(line[NO_SND_ID], msg);
            sms_count_++ ;
            is_sending_ = true;
        }
    if(is_sending_)
        logDebug(l)<<QString(tr("В очередь добавлено %1 SMS")).arg(sms_count_);
}

void ModemManager::smsSended(const QString &id, int result)
{
    static int wrong_sms = 0;
    sms_count_--;
    if(sms_count_ <= 0)
        {
            is_sending_ = false;
            if(wrong_sms == 0)
                logInfo(l)<<tr("Все SMS отправлены");
            else
                logError(l)<<QString(tr("%1 SMS не отправлено")).arg(wrong_sms);
            requestModemMoney();
            wrong_sms = 0;
        }
    if(result != QTelephony::OK)
        {
            wrong_sms++;
            logError(l)<<QString(tr("Отправка SMS завершилась неудачей. Код ошибки %1")).arg(result);
        }


    ArchiveDB::instance()->setOutgoingState(id, result);
}

void ModemManager::ussd(const QString& s)
{
    QRegularExpression txt("\"[\\w|\\s|/|\\S]*\\.");
    QRegularExpressionMatch match = txt.match(s);
    bool ok = false;
    if(match.hasMatch())
        {
            QRegularExpression nmb("[+-]?\\d+\\.?\\d*");
            QRegularExpressionMatch num_m = nmb.match(match.captured(0));
            if(num_m.hasMatch())
                {
                    num_m.captured(0).toDouble(&ok);
                    if(ok)
                        modem_money_ = num_m.captured(0);
                    else
                        modem_money_ = "";
                }
        }
    if(ok == false)
        {
            logError(l)<<QString(tr("Невозможно выделить остаток на счету из сообщения \"%1\"")).arg(s);
        }
}

void ModemManager::phoneBookEntry(const QString& str)
{
    static QStringList list;
    QString res = str;
    res.remove("+CPBR:").remove("\"");
    QStringList ls = res.split(",");
    if(ls.size() == 4)
        {
            list.append(QString("%1;%2").arg(ls.at(3)).arg(ls.at(1)));
        }

    std::sort(list.begin(),list.end());

    qDebug()<<"================================================================";
    for(const QString& line : list)
        std::cout<<line.toStdString()<<std::endl;
    qDebug()<<"================================================================";
}

